/** @file
  IP6 option support functions and routines.

  Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Ip6Impl.h"

/**
  Validate the IP6 option format for both the packets we received
  and that we will transmit. It will compute the ICMPv6 error message fields
  if the option is malformatted.

  @param[in]  IpSb              The IP6 service data.
  @param[in]  Packet            The to be validated packet.
  @param[in]  Option            The first byte of the option.
  @param[in]  OptionLen         The length of the whole option.
  @param[in]  Pointer           Identifies the octet offset within
                                the invoking packet where the error was detected.


  @retval TRUE     The option is properly formatted.
  @retval FALSE    The option is malformatted.

**/
BOOLEAN
Ip6IsOptionValid (
  IN IP6_SERVICE            *IpSb,
  IN NET_BUF                *Packet,
  IN UINT8                  *Option,
  IN UINT8                  OptionLen,
  IN UINT32                 Pointer
  )
{
  UINT8                      Offset;
  UINT8                      OptionType;

  Offset = 0;

  while (Offset < OptionLen) {
    OptionType = *(Option + Offset);

    switch (OptionType) {
    case Ip6OptionPad1:
      //
      // It is a Pad1 option
      //
      Offset++;
      break;
    case Ip6OptionPadN:
      //
      // It is a PadN option
      //
      Offset = (UINT8) (Offset + *(Option + Offset + 1) + 2);
      break;
    case Ip6OptionRouterAlert:
      //
      // It is a Router Alert Option
      //
      Offset += 4;
      break;
    default:
      //
      // The highest-order two bits specify the action must be taken if
      // the processing IPv6 node does not recognize the option type.
      //
      switch (OptionType & Ip6OptionMask) {
      case Ip6OptionSkip:
        Offset = (UINT8) (Offset + *(Option + Offset + 1));
        break;
      case Ip6OptionDiscard:
        return FALSE;
      case Ip6OptionParameterProblem:
        Pointer = Pointer + Offset + sizeof (EFI_IP6_HEADER);
        Ip6SendIcmpError (
          IpSb,
          Packet,
          NULL,
          &Packet->Ip.Ip6->SourceAddress,
          ICMP_V6_PARAMETER_PROBLEM,
          2,
          &Pointer
          );
        return FALSE;
      case Ip6OptionMask:
        if (!IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) {
          Pointer = Pointer + Offset + sizeof (EFI_IP6_HEADER);
          Ip6SendIcmpError (
            IpSb,
            Packet,
            NULL,
            &Packet->Ip.Ip6->SourceAddress,
            ICMP_V6_PARAMETER_PROBLEM,
            2,
            &Pointer
            );
        }

        return FALSE;
        break;
      }

      break;
    }

  }

  return TRUE;
}

/**
  Validate the IP6 option format for both the packets we received
  and that we will transmit. It supports the defined options in Neighbor
  Discovery messages.

  @param[in]  Option            The first byte of the option.
  @param[in]  OptionLen         The length of the whole option.

  @retval TRUE     The option is properly formatted.
  @retval FALSE    The option is malformatted.

**/
BOOLEAN
Ip6IsNDOptionValid (
  IN UINT8                  *Option,
  IN UINT16                 OptionLen
  )
{
  UINT16                    Offset;
  UINT8                     OptionType;
  UINT16                    Length;

  Offset = 0;

  while (Offset < OptionLen) {
    OptionType = *(Option + Offset);
     Length    = (UINT16) (*(Option + Offset + 1) * 8);

    switch (OptionType) {
    case Ip6OptionPrefixInfo:
      if (Length != 32) {
        return FALSE;
      }

      break;

    case Ip6OptionMtu:
      if (Length != 8) {
        return FALSE;
      }

      break;

    default:
      //
      // Check the length of Ip6OptionEtherSource, Ip6OptionEtherTarget, and
      // Ip6OptionRedirected here. For unrecognized options, silently ignore
      // and continue processsing the message.
      //
      if (Length == 0) {
        return FALSE;
      }

      break;
    }

    Offset = (UINT16) (Offset + Length);
  }

  return TRUE;
}


/**
  Validate whether the NextHeader is a known valid protocol or one of the user configured
  protocols from the upper layer.

  @param[in]  IpSb          The IP6 service instance.
  @param[in]  NextHeader    The next header field.

  @retval TRUE              The NextHeader is a known valid protocol or user configured.
  @retval FALSE             The NextHeader is not a known valid protocol.

**/
BOOLEAN
Ip6IsValidProtocol (
  IN IP6_SERVICE            *IpSb,
  IN UINT8                  NextHeader
  )
{
  LIST_ENTRY                *Entry;
  IP6_PROTOCOL              *IpInstance;

  if (NextHeader == EFI_IP_PROTO_TCP ||
      NextHeader == EFI_IP_PROTO_UDP ||
      NextHeader == IP6_ICMP ||
      NextHeader == IP6_ESP
      ) {
    return TRUE;
  }

  if (IpSb == NULL) {
    return FALSE;
  }

  if (IpSb->Signature != IP6_SERVICE_SIGNATURE) {
    return FALSE;
  }

  NET_LIST_FOR_EACH (Entry, &IpSb->Children) {
    IpInstance = NET_LIST_USER_STRUCT_S (Entry, IP6_PROTOCOL, Link, IP6_PROTOCOL_SIGNATURE);
    if (IpInstance->State == IP6_STATE_CONFIGED) {
      if (IpInstance->ConfigData.DefaultProtocol == NextHeader) {
        return TRUE;
      }
    }
  }

  return FALSE;
}

/**
  Validate the IP6 extension header format for both the packets we received
  and that we will transmit. It will compute the ICMPv6 error message fields
  if the option is mal-formatted.

  @param[in]  IpSb          The IP6 service instance. This is an optional parameter.
  @param[in]  Packet        The data of the packet. Ignored if NULL.
  @param[in]  NextHeader    The next header field in IPv6 basic header.
  @param[in]  ExtHdrs       The first byte of the option.
  @param[in]  ExtHdrsLen    The length of the whole option.
  @param[in]  Rcvd          The option is from the packet we received if TRUE,
                            otherwise, the option we want to transmit.
  @param[out] FormerHeader  The offset of NextHeader which points to Fragment
                            Header when we received, of the ExtHdrs.
                            Ignored if we transmit.
  @param[out] LastHeader    The pointer of NextHeader of the last extension
                            header processed by IP6.
  @param[out] RealExtsLen   The length of extension headers processed by IP6 layer.
                            This is an optional parameter that may be NULL.
  @param[out] UnFragmentLen The length of unfragmented length of extension headers.
                            This is an optional parameter that may be NULL.
  @param[out] Fragmented    Indicate whether the packet is fragmented.
                            This is an optional parameter that may be NULL.

  @retval     TRUE          The option is properly formatted.
  @retval     FALSE         The option is malformatted.

**/
BOOLEAN
Ip6IsExtsValid (
  IN IP6_SERVICE            *IpSb           OPTIONAL,
  IN NET_BUF                *Packet         OPTIONAL,
  IN UINT8                  *NextHeader,
  IN UINT8                  *ExtHdrs,
  IN UINT32                 ExtHdrsLen,
  IN BOOLEAN                Rcvd,
  OUT UINT32                *FormerHeader   OPTIONAL,
  OUT UINT8                 **LastHeader,
  OUT UINT32                *RealExtsLen    OPTIONAL,
  OUT UINT32                *UnFragmentLen  OPTIONAL,
  OUT BOOLEAN               *Fragmented     OPTIONAL
  )
{
  UINT32                     Pointer;
  UINT32                     Offset;
  UINT8                      *Option;
  UINT8                      OptionLen;
  BOOLEAN                    Flag;
  UINT8                      CountD;
  UINT8                      CountA;
  IP6_FRAGMENT_HEADER        *FragmentHead;
  UINT16                     FragmentOffset;
  IP6_ROUTING_HEADER         *RoutingHead;

  if (RealExtsLen != NULL) {
    *RealExtsLen = 0;
  }

  if (UnFragmentLen != NULL) {
    *UnFragmentLen = 0;
  }

  if (Fragmented != NULL) {
    *Fragmented = FALSE;
  }

  *LastHeader = NextHeader;

  if (ExtHdrs == NULL && ExtHdrsLen == 0) {
    return TRUE;
  }

  if ((ExtHdrs == NULL && ExtHdrsLen != 0) || (ExtHdrs != NULL && ExtHdrsLen == 0)) {
    return FALSE;
  }

  Pointer = 0;
  Offset  = 0;
  Flag    = FALSE;
  CountD  = 0;
  CountA  = 0;

  while (Offset <= ExtHdrsLen) {

    switch (*NextHeader) {
    case IP6_HOP_BY_HOP:
      if (Offset != 0) {
        if (!Rcvd) {
          return FALSE;
        }
        //
        // Hop-by-Hop Options header is restricted to appear immediately after an IPv6 header only.
        // If not, generate a ICMP parameter problem message with code value of 1.
        //
        if (Pointer == 0) {
          Pointer = sizeof (EFI_IP6_HEADER);
        } else {
          Pointer = Offset + sizeof (EFI_IP6_HEADER);
        }

        if ((IpSb != NULL) && (Packet != NULL) &&
            !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) {
          Ip6SendIcmpError (
            IpSb,
            Packet,
            NULL,
            &Packet->Ip.Ip6->SourceAddress,
            ICMP_V6_PARAMETER_PROBLEM,
            1,
            &Pointer
            );
        }
        return FALSE;
      }

      Flag = TRUE;

    //
    // Fall through
    //
    case IP6_DESTINATION:
      if (*NextHeader == IP6_DESTINATION) {
        CountD++;
      }

      if (CountD > 2) {
        return FALSE;
      }

      NextHeader = ExtHdrs + Offset;
      Pointer    = Offset;

      Offset++;
      Option     = ExtHdrs + Offset;
      OptionLen  = (UINT8) ((*Option + 1) * 8 - 2);
      Option++;
      Offset++;

      if (IpSb != NULL && Packet != NULL && !Ip6IsOptionValid (IpSb, Packet, Option, OptionLen, Offset)) {
        return FALSE;
      }

      Offset = Offset + OptionLen;

      if (Flag) {
        if (UnFragmentLen != NULL) {
          *UnFragmentLen = Offset;
        }

        Flag = FALSE;
      }

      break;

    case IP6_ROUTING:
      NextHeader = ExtHdrs + Offset;
      RoutingHead = (IP6_ROUTING_HEADER *) NextHeader;

      //
      // Type 0 routing header is defined in RFC2460 and deprecated in RFC5095.
      // Thus all routing types are processed as unrecognized.
      //
      if (RoutingHead->SegmentsLeft == 0) {
        //
        // Ignore the routing header and proceed to process the next header.
        //
        Offset = Offset + (RoutingHead->HeaderLen + 1) * 8;

        if (UnFragmentLen != NULL) {
          *UnFragmentLen = Offset;
        }

      } else {
        //
        // Discard the packet and send an ICMP Parameter Problem, Code 0, message
        // to the packet's source address, pointing to the unrecognized routing
        // type.
        //
        Pointer = Offset + 2 + sizeof (EFI_IP6_HEADER);
        if ((IpSb != NULL) && (Packet != NULL) &&
            !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) {
          Ip6SendIcmpError (
            IpSb,
            Packet,
            NULL,
            &Packet->Ip.Ip6->SourceAddress,
            ICMP_V6_PARAMETER_PROBLEM,
            0,
            &Pointer
            );
        }

        return FALSE;
      }

      break;

    case IP6_FRAGMENT:

      //
      // RFC2402, AH header should after fragment header.
      //
      if (CountA > 1) {
        return FALSE;
      }

      //
      // RFC2460, ICMP Parameter Problem message with code 0 should be sent
      // if the length of a fragment is not a multiple of 8 octets and the M
      // flag of that fragment is 1, pointing to the Payload length field of the
      // fragment packet.
      //
      if (IpSb != NULL && Packet != NULL && (ExtHdrsLen % 8) != 0) {
        //
        // Check whether it is the last fragment.
        //
        FragmentHead = (IP6_FRAGMENT_HEADER *) (ExtHdrs + Offset);
        if (FragmentHead == NULL) {
          return FALSE;
        }

        FragmentOffset = NTOHS (FragmentHead->FragmentOffset);

        if (((FragmentOffset & 0x1) == 0x1) &&
            !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) {
          Pointer = sizeof (UINT32);
          Ip6SendIcmpError (
            IpSb,
            Packet,
            NULL,
            &Packet->Ip.Ip6->SourceAddress,
            ICMP_V6_PARAMETER_PROBLEM,
            0,
            &Pointer
            );
          return FALSE;
        }
      }

      if (Fragmented != NULL) {
        *Fragmented = TRUE;
      }

      if (Rcvd && FormerHeader != NULL) {
        *FormerHeader = (UINT32) (NextHeader - ExtHdrs);
      }

      NextHeader = ExtHdrs + Offset;
      Offset     = Offset + 8;
      break;

    case IP6_AH:
      if (++CountA > 1) {
        return FALSE;
      }

      Option     = ExtHdrs + Offset;
      NextHeader = Option;
      Option++;
      //
      // RFC2402, Payload length is specified in 32-bit words, minus "2".
      //
      OptionLen  = (UINT8) ((*Option + 2) * 4);
      Offset     = Offset + OptionLen;
      break;

    case IP6_NO_NEXT_HEADER:
      *LastHeader = NextHeader;
      return FALSE;
      break;

    default:
      if (Ip6IsValidProtocol (IpSb, *NextHeader)) {

        *LastHeader = NextHeader;

        if (RealExtsLen != NULL) {
          *RealExtsLen = Offset;
        }

        return TRUE;
      }

      //
      // The Next Header value is unrecognized by the node, discard the packet and
      // send an ICMP parameter problem message with code value of 1.
      //
      if (Offset == 0) {
        //
        // The Next Header directly follows IPv6 basic header.
        //
        Pointer = 6;
      } else {
        if (Pointer == 0) {
          Pointer = sizeof (EFI_IP6_HEADER);
        } else {
          Pointer = Offset + sizeof (EFI_IP6_HEADER);
        }
      }

      if ((IpSb != NULL) && (Packet != NULL) &&
          !IP6_IS_MULTICAST (&Packet->Ip.Ip6->DestinationAddress)) {
        Ip6SendIcmpError (
          IpSb,
          Packet,
          NULL,
          &Packet->Ip.Ip6->SourceAddress,
          ICMP_V6_PARAMETER_PROBLEM,
          1,
          &Pointer
          );
      }
      return FALSE;
    }
  }

  *LastHeader = NextHeader;

  if (RealExtsLen != NULL) {
    *RealExtsLen = Offset;
  }

  return TRUE;
}

/**
  Generate an IPv6 router alert option in network order and output it through Buffer.

  @param[out]     Buffer         Points to a buffer to record the generated option.
  @param[in, out] BufferLen      The length of Buffer, in bytes.
  @param[in]      NextHeader     The 8-bit selector indicates the type of header
                                 immediately following the Hop-by-Hop Options header.

  @retval EFI_BUFFER_TOO_SMALL   The Buffer is too small to contain the generated
                                 option. BufferLen is updated for the required size.

  @retval EFI_SUCCESS            The option is generated and filled in to Buffer.

**/
EFI_STATUS
Ip6FillHopByHop (
  OUT UINT8                  *Buffer,
  IN OUT UINTN               *BufferLen,
  IN UINT8                   NextHeader
  )
{
  UINT8                      BufferArray[8];

  if (*BufferLen < 8) {
    *BufferLen = 8;
    return EFI_BUFFER_TOO_SMALL;
  }

  //
  // Form the Hop-By-Hop option in network order.
  // NextHeader (1 octet) + HdrExtLen (1 octet) + RouterAlertOption(4 octets) + PadN
  // The Hdr Ext Len is the length in 8-octet units, and does not including the first 8 octets.
  //
  ZeroMem (BufferArray, sizeof (BufferArray));
  BufferArray[0] = NextHeader;
  BufferArray[2] = 0x5;
  BufferArray[3] = 0x2;
  BufferArray[6] = 1;

  CopyMem (Buffer, BufferArray, sizeof (BufferArray));
  return EFI_SUCCESS;
}

/**
  Insert a Fragment Header to the Extension headers and output it in UpdatedExtHdrs.

  @param[in]  IpSb             The IP6 service instance to transmit the packet.
  @param[in]  NextHeader       The extension header type of first extension header.
  @param[in]  LastHeader       The extension header type of last extension header.
  @param[in]  ExtHdrs          The length of the original extension header.
  @param[in]  ExtHdrsLen       The length of the extension headers.
  @param[in]  FragmentOffset   The fragment offset of the data following the header.
  @param[out] UpdatedExtHdrs   The updated ExtHdrs with Fragment header inserted.
                               It's caller's responsibility to free this buffer.

  @retval EFI_OUT_OF_RESOURCES Failed to finish the operation due to lake of
                               resource.
  @retval EFI_UNSUPPORTED      The extension header specified in ExtHdrs is not
                               supported currently.
  @retval EFI_SUCCESS          The operation performed successfully.

**/
EFI_STATUS
Ip6FillFragmentHeader (
  IN  IP6_SERVICE           *IpSb,
  IN  UINT8                 NextHeader,
  IN  UINT8                 LastHeader,
  IN  UINT8                 *ExtHdrs,
  IN  UINT32                ExtHdrsLen,
  IN  UINT16                FragmentOffset,
  OUT UINT8                 **UpdatedExtHdrs
  )
{
  UINT32                    Length;
  UINT8                     *Buffer;
  UINT32                    FormerHeader;
  UINT32                    Offset;
  UINT32                    Part1Len;
  UINT32                    HeaderLen;
  UINT8                     Current;
  IP6_FRAGMENT_HEADER       FragmentHead;

  if (UpdatedExtHdrs == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Length = ExtHdrsLen + sizeof (IP6_FRAGMENT_HEADER);
  Buffer = AllocatePool (Length);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Offset         = 0;
  Part1Len       = 0;
  FormerHeader   = 0;
  Current        = NextHeader;

  while ((ExtHdrs != NULL) && (Offset <= ExtHdrsLen)) {
    switch (NextHeader) {
    case IP6_ROUTING:
    case IP6_HOP_BY_HOP:
    case IP6_DESTINATION:
      Current      = NextHeader;
      NextHeader   = *(ExtHdrs + Offset);

      if ((Current == IP6_DESTINATION) && (NextHeader != IP6_ROUTING)) {
        //
        // Destination Options header should occur at most twice, once before
        // a Routing header and once before the upper-layer header. Here we
        // find the one before the upper-layer header. Insert the Fragment
        // Header before it.
        //
        CopyMem (Buffer, ExtHdrs, Part1Len);
        *(Buffer + FormerHeader) = IP6_FRAGMENT;
        //
        // Exit the loop.
        //
        Offset = ExtHdrsLen + 1;
        break;
      }


      FormerHeader = Offset;
      HeaderLen    = (*(ExtHdrs + Offset + 1) + 1) * 8;
      Part1Len     = Part1Len + HeaderLen;
      Offset       = Offset + HeaderLen;
      break;

    case IP6_FRAGMENT:
      Current    = NextHeader;

      if (Part1Len != 0) {
        CopyMem (Buffer, ExtHdrs, Part1Len);
      }

      *(Buffer + FormerHeader) = IP6_FRAGMENT;

      //
      // Exit the loop.
      //
      Offset = ExtHdrsLen + 1;
      break;

    case IP6_AH:
      Current    = NextHeader;
      NextHeader = *(ExtHdrs + Offset);
      //
      // RFC2402, Payload length is specified in 32-bit words, minus "2".
      //
      HeaderLen  = (*(ExtHdrs + Offset + 1) + 2) * 4;
      Part1Len   = Part1Len + HeaderLen;
      Offset     = Offset + HeaderLen;
      break;

    default:
      if (Ip6IsValidProtocol (IpSb, NextHeader)) {
        Current = NextHeader;
        CopyMem (Buffer, ExtHdrs, Part1Len);
        *(Buffer + FormerHeader) = IP6_FRAGMENT;
        //
        // Exit the loop.
        //
        Offset = ExtHdrsLen + 1;
        break;
      }

      FreePool (Buffer);
      return EFI_UNSUPPORTED;
    }
  }

  //
  // Append the Fragment header. If the fragment offset indicates the fragment
  // is the first fragment.
  //
  if ((FragmentOffset & IP6_FRAGMENT_OFFSET_MASK) == 0) {
    FragmentHead.NextHeader = Current;
  } else {
    FragmentHead.NextHeader = LastHeader;
  }

  FragmentHead.Reserved       = 0;
  FragmentHead.FragmentOffset = HTONS (FragmentOffset);
  FragmentHead.Identification = mIp6Id;

  CopyMem (Buffer + Part1Len, &FragmentHead, sizeof (IP6_FRAGMENT_HEADER));

  if ((ExtHdrs != NULL) && (Part1Len < ExtHdrsLen)) {
    //
    // Append the part2 (fragmentable part) of Extension headers
    //
    CopyMem (
      Buffer + Part1Len + sizeof (IP6_FRAGMENT_HEADER),
      ExtHdrs + Part1Len,
      ExtHdrsLen - Part1Len
      );
  }

  *UpdatedExtHdrs = Buffer;

  return EFI_SUCCESS;
}

